//
//  OpenReader.m
//  Vienna
//
//  Created by Adam Hartford on 7/7/11.
//  Copyright 2011-2018 Vienna contributors (see menu item 'About Vienna' for list of contributors). All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

/*
 * Unofficial references for OpenReader API :
 * https://rss-sync.github.io/Open-Reader-API/
 * https://ranchero.com/downloads/GoogleReaderAPI-2009.pdf
 * https://feedhq.readthedocs.io/en/latest/api/index.html
 * https://github.com/theoldreader/api
 * https://www.inoreader.com/developers/
 */

#import "OpenReader.h"

@import os.log;

#import "Constants.h"
#import "URLRequestExtensions.h"
#import "Folder.h"
#import "Database.h"
#import "RefreshManager.h"
#import "Preferences.h"
#import "StringExtensions.h"
#import "NSNotificationAdditions.h"
#import "Keychain.h"
#import "ActivityItem.h"
#import "Article.h"

static NSString * const LoginBaseURL = @"%@://%@/accounts/ClientLogin?accountType=GOOGLE&service=reader";
static NSString * const ClientName = @"ViennaRSS";

typedef NS_ENUM (NSInteger, OpenReaderStatus) {
    notAuthenticated = 0,
    waitingClientToken,
    missingTToken,
    waitingTToken,
    fullyAuthenticated,
    clientTokenError
};

#define VNA_LOG os_log_create("--", "OpenReader")

@interface OpenReader ()

@property (readwrite, copy) NSString *statusMessage;
@property (readwrite, nonatomic) NSUInteger countOfNewArticles;
@property NSString *tToken;
@property NSString *clientAuthToken;
@property (nonatomic) NSTimer *tTokenTimer;
@property (nonatomic) NSTimer *clientAuthTimer;
@property (nonatomic) dispatch_queue_t asyncQueue;
@property (nonatomic) OpenReaderStatus openReaderStatus;

@end

@implementation OpenReader {
    NSString *latestAlertDescription;

    // host specific variables
    NSString *openReaderHost;
    NSString *openReaderScheme;
    NSString *username;
    NSString *password;
    NSString *APIBaseURL;
    BOOL hostSendsHexaItemId;
    BOOL hostRequiresSParameter;
    BOOL hostRequiresHexaForFeedId;
    BOOL hostRequiresInoreaderHeaders;
}

# pragma mark initialization

-(instancetype)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
        _openReaderStatus = notAuthenticated;
        _countOfNewArticles = 0;
        latestAlertDescription = @"";
        openReaderHost = nil;
        username = nil;
        password = nil;
        APIBaseURL = nil;
        _asyncQueue = dispatch_queue_create("uk.co.opencommunity.vienna2.openReaderTasks", DISPATCH_QUEUE_SERIAL);
        [NSNotificationCenter.defaultCenter addObserver:self
                                               selector:@selector(handleRefreshStatusChange:)
                                                   name:MA_Notify_RefreshStatus object:nil];
    }

    return self;
} // init

/* sharedManager
 * Returns the single instance of the Open Reader.
 */
+(OpenReader *)sharedManager
{
    // Singleton
    static OpenReader *_openReader = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _openReader = [[OpenReader alloc] init];
        [_openReader configureForSpecificHost];
    });
    return _openReader;
}

# pragma mark user authentication and requests preparation

/* prepare a NSMutableURLRequest from an NSURL
 */
-(NSMutableURLRequest *)requestFromURL:(NSURL *)url
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [self addClientTokenToRequest:request];
    if (self.openReaderStatus != clientTokenError) {
        [self specificHeadersPrepare:request];
    }
    return request;
}

/* prepare a FormData request from an NSURL and pass the T token
 */
-(NSMutableURLRequest *)authentifiedFormRequestFromURL:(NSURL *)url
{
    NSMutableURLRequest *request = [self requestFromURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [self getTokenForRequest:request];
    return request;
}

-(void)specificHeadersPrepare:(NSMutableURLRequest *)request
{
    if (hostRequiresInoreaderHeaders) {
        [request setValue:[Preferences standardPreferences].syncingAppId forHTTPHeaderField:@"AppID"];
        [request setValue:[Preferences standardPreferences].syncingAppKey forHTTPHeaderField:@"AppKey"];
        [request setValue:@"Mozilla/5.0 (compatible)" forHTTPHeaderField:@"User-Agent"];
    }
}

/* pass the GoogleLogin client authentication token
 */
-(void)addClientTokenToRequest:(NSMutableURLRequest *)clientRequest
{
    static NSOperation * clientAuthOperation;
    dispatch_semaphore_t sema;

    // Do nothing if syncing is disabled in preferences
    if (![Preferences standardPreferences].syncGoogleReader) {
        return;
    }

    switch (self.openReaderStatus) {
    case clientTokenError:
        return;
    case fullyAuthenticated:
    case waitingTToken:
    case missingTToken:
        break;
    case waitingClientToken:
        if (!clientAuthOperation.isFinished && sema != nil) {
            dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        }
        break;
    case notAuthenticated:
        [self configureForSpecificHost];
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:LoginBaseURL, openReaderScheme, openReaderHost]];
        NSMutableURLRequest *myRequest = [NSMutableURLRequest requestWithURL:url];
        myRequest.HTTPMethod = @"POST";
        [myRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        [self specificHeadersPrepare:myRequest];
        username = [Preferences standardPreferences].syncingUser;
        // restore from keychain
        password = [VNAKeychain getGenericPasswordFromKeychain:username serviceName:@"Vienna sync"];
        [myRequest vna_setPostValue:username forKey:@"Email"];
        [myRequest vna_setPostValue:password forKey:@"Passwd"];
        // semaphore with count equal to zero for synchronizing completion of work
        sema = dispatch_semaphore_create(0);
        // prepare authentication operation
        clientAuthOperation = [NSBlockOperation blockOperationWithBlock:^(void) {
            NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:myRequest
                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                    self.openReaderStatus = notAuthenticated;
                    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
					if (error) {
						self.openReaderStatus = clientTokenError;
						NSString * alertDescription = error.localizedDescription;
						if (![alertDescription isEqualToString:self->latestAlertDescription]) {
						    [nc vna_postNotificationOnMainThreadWithName:MA_Notify_GoogleAuthFailed object:alertDescription];
						    self->latestAlertDescription = alertDescription;
						}
					} else if (((NSHTTPURLResponse *)response).statusCode != 200) {
						self.openReaderStatus = clientTokenError;
						NSString * alertDescription = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
						if (![alertDescription isEqualToString:self->latestAlertDescription]) {
						    [nc vna_postNotificationOnMainThreadWithName:MA_Notify_GoogleAuthFailed object:alertDescription];
						    self->latestAlertDescription = alertDescription;
						}
 					} else {         // statusCode 200
						self->latestAlertDescription = @"";
						NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
						NSArray *components = [response componentsSeparatedByString:@"\n"];
						for (NSString * item in components) {
							if([item hasPrefix:@"Auth="]) {
								self.clientAuthToken = [item substringFromIndex:5];
                                if ([self.clientAuthToken isEqualToString:@"(null)"] || [self.clientAuthToken isEqualToString:@""]) {
                                    self.openReaderStatus = clientTokenError;
                                    [nc vna_postNotificationOnMainThreadWithName:MA_Notify_GoogleAuthFailed object:@""];
                                    self->latestAlertDescription = @"";
								} else {
								    self.openReaderStatus = missingTToken;								
								}
								break;
							}
						}
                        if (self.clientAuthTimer == nil || !self.clientAuthTimer.valid) {
                            // new request every 6 days
                            self.clientAuthTimer = [NSTimer scheduledTimerWithTimeInterval:6 * 24 * 3600
                                                                                    target:self
                                                                                  selector:@selector(resetAuthentication)
                                                                                  userInfo:nil
                                                                                   repeats:YES];
                        }
                    } // end error and statusCode tests
                    // Signal that we are done
                    dispatch_semaphore_signal(sema);
            }];
            task.priority = NSURLSessionTaskPriorityHigh;
            [task resume];
        }]; // end NSBlockOperation definition
        self.openReaderStatus = waitingClientToken;
        self.statusMessage = NSLocalizedString(@"Authenticating on Open Reader", nil);
        clientAuthOperation.queuePriority = NSOperationQueuePriorityHigh;
        [clientAuthOperation start];
        // Now we wait until the task response block will send a signal
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        break;
    }

    [clientRequest addValue:[NSString stringWithFormat:@"GoogleLogin auth=%@", self.clientAuthToken] forHTTPHeaderField:@"Authorization"];
} // addClientTokenToRequest

/* configures oneself regarding host, username and password
 */
-(void)configureForSpecificHost
{
    // restore from Preferences
    Preferences *prefs = [Preferences standardPreferences];
    openReaderHost = prefs.syncServer;
    openReaderScheme = prefs.syncScheme;
    if (!openReaderScheme) {
        openReaderScheme = @"https";
    }
    // default settings
    hostSendsHexaItemId = NO;
    hostRequiresSParameter = NO;
    hostRequiresHexaForFeedId = NO;
    hostRequiresInoreaderHeaders = NO;
    // settings for specific kind of servers
    if ([openReaderHost isEqualToString:@"theoldreader.com"]) {
        hostSendsHexaItemId = YES;
        hostRequiresSParameter = YES;
        hostRequiresHexaForFeedId = YES;
    }
    if ([openReaderHost rangeOfString:@"inoreader.com"].length != 0) {
        hostRequiresInoreaderHeaders = YES;
    }
    APIBaseURL = [NSString stringWithFormat:@"%@://%@/reader/api/0/", openReaderScheme, openReaderHost];
} // configureForSpecificHost

/* pass the T token
 */
-(void)getTokenForRequest:(NSMutableURLRequest *)clientRequest
{
    static NSOperation * tTokenOperation;
    dispatch_semaphore_t sema;

    if (self.openReaderStatus == clientTokenError) {
        return;
    } else if (self.openReaderStatus == fullyAuthenticated) {
        [clientRequest vna_setPostValue:self.tToken forKey:@"T"];
        return;
    } else {
        // we might get here when status is missingTToken or waitingClientToken or notAuthenticated
        NSMutableURLRequest * myRequest = [self requestFromURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@token", APIBaseURL]]];
        self.openReaderStatus = waitingTToken;
        // semaphore with count equal to zero for synchronizing completion of work
        sema = dispatch_semaphore_create(0);
        tTokenOperation = [NSBlockOperation blockOperationWithBlock:^(void) {
          NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:myRequest
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                if (error) {
                    NSLog(@"tTokenOperation error for URL %@", myRequest.URL);
                    NSLog(@"Request headers %@", myRequest.allHTTPHeaderFields);
                    NSLog(@"Request body %@", [[NSString alloc] initWithData:myRequest.HTTPBody encoding:NSUTF8StringEncoding]);
                    NSLog(@"Response headers %@", ((NSHTTPURLResponse *)response).allHeaderFields);
                    NSLog(@"Response data %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
                    self.tToken = nil;
                    self.openReaderStatus = missingTToken;
                } else {
                    self.tToken = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                    if ([self.tToken isEqualToString:@"(null)"] || [self.tToken isEqualToString:@""]) {
                        self.openReaderStatus = missingTToken;
                    } else {
                        self.openReaderStatus = fullyAuthenticated;
                        if (self.tTokenTimer == nil || !self.tTokenTimer.valid) {
                            //tokens expire after 30 minutes : renew them every 25 minutes
                            self.tTokenTimer = [NSTimer scheduledTimerWithTimeInterval:25 * 60
                                                                                target:self
                                                                              selector:@selector(renewTToken)
                                                                              userInfo:nil
                                                                               repeats:YES];
                        }
                    }
                }
                // Signal that we are done with the synchronous task
                dispatch_semaphore_signal(sema);
            }];
            task.priority = NSURLSessionTaskPriorityHigh;
            [task resume];
        }];
        [tTokenOperation start];
        // we wait until the task response block above will send a signal
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        [clientRequest vna_setPostValue:self.tToken forKey:@"T"];
    } // missingTToken or waitingClientToken or notAuthenticated
} // getTokenForRequest

-(void)renewTToken
{
    self.openReaderStatus = missingTToken;
    self.tToken = nil;
    [self getTokenForRequest:nil];
}

-(void)clearAuthentication
{
    self.openReaderStatus = notAuthenticated;
    self.clientAuthToken = nil;
    self.tToken = nil;
}

-(void)resetAuthentication
{
    [self clearAuthentication];
    [self addClientTokenToRequest:nil];
}

/* handleRefreshStatusChange
 * Handle a change of the network queue running.
 */
-(void)handleRefreshStatusChange:(NSNotification *)nc
{
    if (self.openReaderStatus == clientTokenError) {
        self.openReaderStatus = notAuthenticated;
    }
}

# pragma mark default handlers

// default handler for didFailSelector
-(void)requestFailed:(NSMutableURLRequest *)request response:(NSURLResponse *)response error:(NSError *)error
{
    os_log_error(VNA_LOG, "Failed on request %{mask.hash}@. Reason: %{public}@", request.URL, error.localizedDescription);
    if (error.code == NSURLErrorUserAuthenticationRequired) {   //Error caused by lack of authentication
        [self clearAuthentication];
    }
}

// default handler for didFinishSelector
-(void)requestFinished:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    NSString *requestResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (![requestResponse isEqualToString:@"OK"]) {
        os_log_error(VNA_LOG, "Error (response: %{public}@, status code: %ld) on request %{mask.hash}@", requestResponse, ((NSHTTPURLResponse *)response).statusCode, request.URL);
    }
}

# pragma mark status accessors

-(BOOL)isReady
{
    return (self.openReaderStatus == fullyAuthenticated && self.tTokenTimer != nil);
}

/* resetCountOfNewArticles
 */
-(void)resetCountOfNewArticles
{
    self.countOfNewArticles=0;
}

# pragma mark operations

-(void)refreshFeed:(Folder *)thisFolder withLog:(ActivityItem *)aItem shouldIgnoreArticleLimit:(BOOL)ignoreLimit
{
    //This is a workaround throw a BAD folderupdate value on DB
    NSString *folderLastUpdateString = ignoreLimit ? @"0" : thisFolder.lastUpdateString;
    if (folderLastUpdateString == nil
        || [folderLastUpdateString isEqualToString:@""]
        || [folderLastUpdateString isEqualToString:@"(null)"])
    {
        folderLastUpdateString = @"0";
    }

    NSString *itemsLimitation;
    if (ignoreLimit) {
        itemsLimitation = @"&n=10000";         //just stay reasonable…
    } else {
        //Note : we don't set "r" (sorting order) here.
        //But according to some documentation, Google Reader and TheOldReader
        //need "r=o" order to make the "ot" time limitation work.
        //In fact, Vienna used successfully "r=n" with Google Reader.
        @try {
            double limit = folderLastUpdateString.doubleValue - 15*60;
            if (limit < 0.0f) {
                limit = 0.0;
            }
            NSString *startEpoch = @(limit).stringValue;
            itemsLimitation = [NSString stringWithFormat:@"&ot=%@&n=1000", startEpoch];
        } @catch (NSException *exception) {
            itemsLimitation = @"&n=1000";
        }
        if (thisFolder.flags & VNAFolderFlagBuggySync) {
            itemsLimitation = @"&n=1000";
        }
    }

    NSString *feedIdentifier = thisFolder.remoteId;
    if (feedIdentifier == nil || ![feedIdentifier hasPrefix:@"feed/"]) {
            return;
    }

    NSURL *refreshFeedUrl =
        [NSURL URLWithString:[NSString stringWithFormat:
                              @"%@stream/contents/%@?client=%@&comments=false&likes=false%@&output=json",
                              APIBaseURL,
                              [OpenReader escapeFeedId:feedIdentifier], ClientName, itemsLimitation]];

    NSMutableURLRequest *request = [self requestFromURL:refreshFeedUrl];
    [request vna_setUserInfo:
        @{ @"folder": thisFolder, @"log": aItem, @"lastupdatestring": folderLastUpdateString, @"type": @(MA_Refresh_GoogleFeed) }];

    // Request id's of unread items
    NSString *args =
        [NSString stringWithFormat:@"?client=%@&s=%@&xt=user/-/state/com.google/read&n=1000&output=json", ClientName,
         [OpenReader escapeFeedId:feedIdentifier]];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@%@", APIBaseURL, @"stream/items/ids", args]];
    NSMutableURLRequest *request2 = [self requestFromURL:url];
    [request2 vna_setUserInfo:@{ @"folder": thisFolder, @"log": aItem }];

    // Request id's of starred items
    // Note: Inoreader requires syntax "it=user/-/state/...", while TheOldReader ignores it and requires "s=user/-/state/..."
    NSString *starredSelector;
    if (hostRequiresSParameter) {
        starredSelector = @"s=user/-/state/com.google/starred";
    } else {
        starredSelector = @"it=user/-/state/com.google/starred";
    }

    NSString *args3 =
        [NSString stringWithFormat:@"?client=%@&s=%@&%@&n=1000&output=json", ClientName,
         [OpenReader escapeFeedId:feedIdentifier], starredSelector];
    NSURL *url3 = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@%@", APIBaseURL, @"stream/items/ids", args3]];
    NSMutableURLRequest *request3 = [self requestFromURL:url3];
    [request3 vna_setUserInfo:@{ @"folder": thisFolder, @"log": aItem }];

    __weak typeof(self) weakSelf = self;
    NSOperation * op =
        [[RefreshManager sharedManager] addConnection:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf feedRequestFailed:request response:response error:error];
            } else {
                [weakSelf feedRequestDone:request response:response data:data];
            }
    }];

    [[RefreshManager sharedManager] suspendConnectionsQueue];

    NSOperation * op2 =
        [[RefreshManager sharedManager] addConnection:request2 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:request2 response:response error:error];
            } else {
                [weakSelf readRequestDone:request2 response:response data:data];
            }
    }];
    [op2 addDependency:op];

    NSOperation * op3 =
        [[RefreshManager sharedManager] addConnection:request3 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:request3 response:response error:error];
            } else {
                [weakSelf starredRequestDone:request2 response:response data:data];
            }
    }];
    [op3 addDependency:op2];

    [[RefreshManager sharedManager] resumeConnectionsQueue];

} // refreshFeed

// callback : handler for timed out feeds, etc...
-(void)feedRequestFailed:(NSMutableURLRequest *)request response:(NSURLResponse *)response error:(NSError *)error
{
    os_log_error(VNA_LOG, "Open Reader feed request %{mask.hash}@ failed. Reason: %{public}@", request.URL, error.localizedDescription);
    ActivityItem *aItem = ((NSDictionary *)[request vna_userInfo])[@"log"];
    Folder *refreshedFolder = ((NSDictionary *)[request vna_userInfo])[@"folder"];

    [aItem appendDetail:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"Error", nil), error.localizedDescription ]];
    [aItem setStatus:NSLocalizedString(@"Error", nil)];
    [refreshedFolder clearNonPersistedFlag:VNAFolderFlagUpdating];
    [refreshedFolder setNonPersistedFlag:VNAFolderFlagError];
    [refreshedFolder clearNonPersistedFlag:VNAFolderFlagSyncedOK]; // get ready for next request
    [[NSNotificationCenter defaultCenter] vna_postNotificationOnMainThreadWithName:MA_Notify_FoldersUpdated
                                                                            object:@(refreshedFolder.itemId)];
}

// callback
-(void)feedRequestDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    dispatch_async(self.asyncQueue, ^() {
        // TODO : refactor code to separate feed refresh code and UI

        ActivityItem *aItem = ((NSDictionary *)[request vna_userInfo])[@"log"];
        Folder *refreshedFolder = ((NSDictionary *)[request vna_userInfo])[@"folder"];
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;

        if (statusCode == 200) {
            // reset unread statuses in cache : we will receive in -ReadRequestDone: the updated list of unreads
            [refreshedFolder markArticlesInCacheRead];
            NSDictionary *subscriptionsDict;
            NSError *jsonError;
            subscriptionsDict = [NSJSONSerialization JSONObjectWithData:data
                                                                options:NSJSONReadingMutableContainers
                                                                  error:&jsonError];
            NSString *folderLastUpdateString = [subscriptionsDict[@"updated"] stringValue];
            if (folderLastUpdateString == nil
                || [folderLastUpdateString isEqualToString:@""]
                || [folderLastUpdateString isEqualToString:@"(null)"])
            {
                os_log_error(VNA_LOG, "Incoherent data for %{mask.hash}@", request.URL);
                os_log_debug(VNA_LOG, "Feed name for %{mask.hash}@: %@", request.URL, subscriptionsDict[@"title"]);
                os_log_debug(VNA_LOG, "Last check for %{mask.hash}@: %@", request.URL, ((NSDictionary *)[request vna_userInfo])[@"lastupdatestring"]);
                os_log_debug(VNA_LOG, "Last update for %{mask.hash}@: %@", request.URL, folderLastUpdateString);
                os_log_debug(VNA_LOG, "Found %lu items for %{mask.hash}@", (unsigned long)[subscriptionsDict[@"items"] count], request.URL);
                os_log_debug(VNA_LOG, "Subscriptions: %@", subscriptionsDict);
                os_log_debug(VNA_LOG, "Data for %{mask.hash}@: %@", request.URL, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
                //keep the previously recorded one
                folderLastUpdateString = ((NSDictionary *)[request vna_userInfo])[@"lastupdatestring"];
            }

            // Log number of bytes we received
            NSString *byteCount = [NSByteCountFormatter stringFromByteCount:data.length
                                                                 countStyle:NSByteCountFormatterCountStyleFile];
            [aItem appendDetail:[NSString stringWithFormat:NSLocalizedString(@"%@ received",
                                                                             @"Number of bytes received, e.g. 1 MB received"), byteCount]];

            NSMutableArray *articleArray = [NSMutableArray array];

            for (NSDictionary *newsItem in (NSArray *)subscriptionsDict[@"items"]) {

                NSString *articleGuid = newsItem[@"id"];
                Article *article = [[Article alloc] initWithGUID:articleGuid];
                article.folderId = refreshedFolder.itemId;

                if (newsItem[@"author"] != nil) {
                    article.author = newsItem[@"author"];
                } else {
                    article.author = @"";
                }

                if (newsItem[@"content"] != nil) {
                    article.body = newsItem[@"content"][@"content"];
                } else if (newsItem[@"summary"] != nil) {
                    article.body = newsItem[@"summary"][@"content"];
                } else {
                    article.body = @"Not available…";
                }

                for (NSString *category in (NSArray *)newsItem[@"categories"]) {
                    if ([category hasSuffix:@"/read"]) {
                        article.read = YES;
                    }
                    if ([category hasSuffix:@"/starred"]) {
                        article.flagged = YES;
                    }
                    if ([category hasSuffix:@"/kept-unread"]) {
                        article.read = NO;
                    }
                }

                if (newsItem[@"title"] != nil) {
                    article.title = [newsItem[@"title"] vna_summaryTextFromHTML];
                } else {
                    article.title = @"";
                }

                if ([newsItem[@"alternate"] count] != 0) {
                    article.link = newsItem[@"alternate"][0][@"href"];
                } else {
                    article.link = refreshedFolder.feedURL;
                }

                NSString *publishedField = newsItem[@"published"];
                if (publishedField) {
                    article.publicationDate = [NSDate dateWithTimeIntervalSince1970:[publishedField doubleValue]];
                }

                NSString * updatedField = newsItem[@"updated"];
                if (updatedField) {
                    article.lastUpdate = [NSDate dateWithTimeIntervalSince1970:[updatedField doubleValue]];
                }

                if ([newsItem[@"enclosure"] count] != 0) {
                    article.enclosure = newsItem[@"enclosure"][0][@"href"];
                } else {
                    article.enclosure = @"";
                }

                if ([article.enclosure isNotEqualTo:@""]) {
                    [article setHasEnclosure:YES];
                }

                [articleArray addObject:article];
            }

            Database *dbManager = [Database sharedManager];
            NSInteger newArticlesFromFeed = 0;

            // Here's where we add the articles to the database
            if (articleArray.count > 0) {
                [refreshedFolder resetArticleStatuses];
                NSArray *guidHistory = [dbManager guidHistoryForFolderId:refreshedFolder.itemId];

                for (Article *article in articleArray) {
                    if ([refreshedFolder createArticle:article guidHistory:guidHistory])
                    {
                        newArticlesFromFeed++;
                    }
                }

                // Set the last update date for this folder.
                [dbManager setLastUpdate:[NSDate date] forFolder:refreshedFolder.itemId];
            }

            if (folderLastUpdateString == nil
                || [folderLastUpdateString isEqualToString:@""]
                || [folderLastUpdateString isEqualToString:@"(null)"])
            {
                folderLastUpdateString = @"0";
            }

            // Set the last update date given by the Open Reader server for this folder.
            [dbManager setLastUpdateString:folderLastUpdateString forFolder:refreshedFolder.itemId];
            // Set the HTML homepage for this folder.
            // a legal JSON string can have, as its outer "container", either an array or a dictionary/"object"
            if ([subscriptionsDict[@"alternate"] isKindOfClass:[NSArray class]]) {
                [dbManager setHomePage:subscriptionsDict[@"alternate"][0][@"href"]
                             forFolder:refreshedFolder.itemId];
            } else {
                [dbManager setHomePage:subscriptionsDict[@"alternate"][@"href"]
                             forFolder:refreshedFolder.itemId];
            }

            // Add to count of new articles so far
            self.countOfNewArticles += newArticlesFromFeed;

            [refreshedFolder clearNonPersistedFlag:VNAFolderFlagError];
            [refreshedFolder clearNonPersistedFlag:VNAFolderFlagBuggySync];
            // Send status to the activity log
            if (newArticlesFromFeed == 0) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [aItem setStatus:NSLocalizedString(@"No new articles available", nil)];
                });
            } else {
                dispatch_async(dispatch_get_main_queue(), ^{
                    aItem.status = [NSString stringWithFormat:NSLocalizedString(@"%d new articles retrieved", nil), (int)newArticlesFromFeed];
                });
            }
        } else { //other HTTP status response...
            [aItem appendDetail:[NSString stringWithFormat:NSLocalizedString(@"HTTP code %ld reported from server", nil), statusCode]];
            os_log_error(VNA_LOG, "Unexpected status code %ld for request %{mask.hash}@", ((NSHTTPURLResponse *)response).statusCode, request.URL);
            os_log_debug(VNA_LOG, "Data for request %{mask.hash}@: %@", request.URL, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
            dispatch_async(dispatch_get_main_queue(), ^{
                [aItem setStatus:NSLocalizedString(@"Error", nil)];
            });
            if (statusCode == 401 || statusCode == 403) {
                NSString * alertDescription = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                if (![alertDescription isEqualToString:self->latestAlertDescription]) {
                    [nc vna_postNotificationOnMainThreadWithName:MA_Notify_GoogleAuthFailed object:alertDescription];
                    self->latestAlertDescription = alertDescription;
                }
            }
            [refreshedFolder clearNonPersistedFlag:VNAFolderFlagUpdating];
            [refreshedFolder setNonPersistedFlag:VNAFolderFlagError];
            [refreshedFolder clearNonPersistedFlag:VNAFolderFlagSyncedOK];
        }
    });     //block for dispatch_async
} // feedRequestDone

// callback
-(void)readRequestDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    typeof(self) __weak weakSelf = self;
    dispatch_async(self.asyncQueue, ^() {
        Folder *refreshedFolder = ((NSDictionary *)[request vna_userInfo])[@"folder"];
        ActivityItem *aItem = ((NSDictionary *)[request vna_userInfo])[@"log"];
        if (((NSHTTPURLResponse *)response).statusCode == 200) {
            @try {
                NSArray *itemRefsArray;
                NSError *jsonError;
                itemRefsArray = [NSJSONSerialization JSONObjectWithData:data
                                                                options:NSJSONReadingMutableContainers
                                                                  error:&jsonError][@"itemRefs"];
                NSMutableArray *guidArray = [NSMutableArray arrayWithCapacity:itemRefsArray.count];
                for (NSDictionary *itemRef in itemRefsArray) {
                    NSString *guid;
                    typeof(self) strongSelf = weakSelf;
                    if (strongSelf && strongSelf->hostSendsHexaItemId) {
                        guid = [NSString stringWithFormat:@"tag:google.com,2005:reader/item/%@", itemRef[@"id"]];
                    } else {
                        // as described in http://code.google.com/p/google-reader-api/wiki/ItemId
                        // the short version of id is a base 10 signed integer ; the long version includes a 16 characters base 16 representation
                        NSInteger shortId = [itemRef[@"id"] integerValue];
                        guid = [NSString stringWithFormat:@"tag:google.com,2005:reader/item/%016qx", (long long)shortId];
                    }

                    [guidArray addObject:guid];
                    // now, mark relevant articles unread
                    [refreshedFolder articleFromGuid:guid].read = NO;
                }

                [[Database sharedManager] markUnreadArticlesFromFolder:refreshedFolder guidArray:guidArray];
                // reset starred statuses in cache : we will receive in -StarredRequestDone: the updated list
                for (Article *article in refreshedFolder.articles) {
                    article.flagged = NO;
                }
            } @catch (NSException *exception) {
                [aItem appendDetail:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"Error", nil), exception]];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [aItem setStatus:NSLocalizedString(@"Error", nil)];
                });
                [refreshedFolder setNonPersistedFlag:VNAFolderFlagError];
            } // try/catch

            // If this folder also requires an image refresh, add that
            if (refreshedFolder.flags & VNAFolderFlagCheckForImage) {
                [[RefreshManager sharedManager] refreshFavIconForFolder:refreshedFolder];
            }
        } else { //response status other than OK (200)
            [aItem appendDetail:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"Error", nil),
                                    [NSHTTPURLResponse localizedStringForStatusCode:((NSHTTPURLResponse *)response).statusCode]]];
            dispatch_async(dispatch_get_main_queue(), ^{
                [aItem setStatus:NSLocalizedString(@"Error", nil)];
            });
            [refreshedFolder setNonPersistedFlag:VNAFolderFlagError];
        }
    });     //block for dispatch_async
} // readRequestDone

// callback
-(void)starredRequestDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    typeof(self) __weak weakSelf = self;
    dispatch_async(self.asyncQueue, ^() {
        Folder *refreshedFolder = ((NSDictionary *)[request vna_userInfo])[@"folder"];
        ActivityItem *aItem = ((NSDictionary *)[request vna_userInfo])[@"log"];
        if (((NSHTTPURLResponse *)response).statusCode == 200) {
            @try {
                NSArray *itemRefsArray;
                NSError *jsonError;
                itemRefsArray = [NSJSONSerialization JSONObjectWithData:data
                                                                options:NSJSONReadingMutableContainers
                                                                  error:&jsonError][@"itemRefs"];
                NSMutableArray *guidArray = [NSMutableArray arrayWithCapacity:itemRefsArray.count];
                for (NSDictionary *itemRef in itemRefsArray) {
                    NSString *guid;
                    typeof(self) strongSelf = weakSelf;
                    if (strongSelf && strongSelf->hostSendsHexaItemId) {
                        guid = [NSString stringWithFormat:@"tag:google.com,2005:reader/item/%@", itemRef[@"id"]];
                    } else {
                        // as described in http://code.google.com/p/google-reader-api/wiki/ItemId
                        // the short version of id is a base 10 signed integer ; the long version includes a 16 characters base 16 representation
                        NSInteger shortId = [itemRef[@"id"] integerValue];
                        guid = [NSString stringWithFormat:@"tag:google.com,2005:reader/item/%016qx", (long long)shortId];
                    }
                    [guidArray addObject:guid];
                    [refreshedFolder articleFromGuid:guid].flagged = YES;
                }

                [[Database sharedManager] markStarredArticlesFromFolder:refreshedFolder guidArray:guidArray];
            } @catch (NSException *exception) {
                [aItem appendDetail:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"Error", nil), exception]];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [aItem setStatus:NSLocalizedString(@"Error", nil)];
                });
                [refreshedFolder setNonPersistedFlag:VNAFolderFlagError];
            } // try/catch
        } else { //response status other than OK (200)
            [aItem appendDetail:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"Error", nil),
                                    [NSHTTPURLResponse localizedStringForStatusCode:((NSHTTPURLResponse *)response).statusCode]]];
            dispatch_async(dispatch_get_main_queue(), ^{
                [aItem setStatus:NSLocalizedString(@"Error", nil)];
            });
            [refreshedFolder setNonPersistedFlag:VNAFolderFlagError];
        }
        // mark end of feed refresh
        [refreshedFolder clearNonPersistedFlag:VNAFolderFlagUpdating];
        NSNotificationCenter * nc = NSNotificationCenter.defaultCenter;
        [nc vna_postNotificationOnMainThreadWithName:MA_Notify_FoldersUpdated object:@(refreshedFolder.itemId)];
        [nc vna_postNotificationOnMainThreadWithName:MA_Notify_ArticleListContentChange object:@(refreshedFolder.itemId)];
    });     //block for dispatch_async
} // starredRequestDone

-(void)loadSubscriptions
{
    [[RefreshManager sharedManager] suspendConnectionsQueue];
    latestAlertDescription = @"";
    NSMutableURLRequest *subscriptionRequest =
        [self requestFromURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@subscription/list?client=%@&output=json", APIBaseURL,
                                                   ClientName]]];
    __weak typeof(self) weakSelf = self;
    NSOperation * subscriptionOperation = [[RefreshManager sharedManager] addConnection:subscriptionRequest
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:subscriptionRequest response:response error:error];
            } else {
                [weakSelf subscriptionsRequestDone:subscriptionRequest response:response data:data];
            }
        }
    ];

    NSMutableURLRequest *unreadCountRequest =
        [self requestFromURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@unread-count?client=%@&output=json&allcomments=false",
                                                   APIBaseURL,
                                                   ClientName]]];
    self.unreadCountOperation = [[RefreshManager sharedManager] addConnection:unreadCountRequest
        completionHandler:^(NSData *data1, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:unreadCountRequest response:response error:error];
            } else {
                [weakSelf unreadCountDone:unreadCountRequest response:response data:data1];
            }
            weakSelf.statusMessage = @"";
        }
    ];
    [self.unreadCountOperation addDependency:subscriptionOperation];
    [[RefreshManager sharedManager] resumeConnectionsQueue];
    self.statusMessage = NSLocalizedString(@"Fetching Open Reader Subscriptions…", nil);
} // loadSubscriptions

// callback 1  to loadSubscriptions
-(void)subscriptionsRequestDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    NSDictionary *subscriptionsDict;
    NSError *jsonError;
    subscriptionsDict = [NSJSONSerialization JSONObjectWithData:data
                                                        options:NSJSONReadingMutableContainers
                                                          error:&jsonError];
    NSMutableArray *localFeeds = [NSMutableArray array];
    NSMutableArray *remoteFeeds = [NSMutableArray array];
    Database * db = [Database sharedManager];
    NSArray *localFolders = db.arrayOfAllFolders;

    for (Folder *f in localFolders) {
        if (f.isOpenReaderFolder && ![f.remoteId isEqualToString:@"0"]) {
            [localFeeds addObject:f.remoteId];
        }
    }

    for (NSDictionary *feed in subscriptionsDict[@"subscriptions"]) {
        NSString *feedID = feed[@"id"];
        if (feedID == nil || ![feedID hasPrefix:@"feed/"]) {
            break;
        }
        NSString *feedURL = feed[@"url"];
        if (!feedURL) {
            feedURL = [feedID stringByReplacingOccurrencesOfString:@"feed/" withString:@"" options:0 range:NSMakeRange(0, 5)];
        }

        NSString *folderName = nil;

        NSArray *categories = feed[@"categories"];
        for (NSDictionary *category in categories) {
            if (category[@"label"]) {
                NSString *label = category[@"label"];
                NSArray *folderNames = [label componentsSeparatedByString:@" — "];
                folderName = folderNames.lastObject;
                // NNW nested folder char: —

                NSMutableArray *params = [NSMutableArray arrayWithObjects:[folderNames mutableCopy], @(VNAFolderTypeRoot), nil];
                [self createFolders:params];
                break;                 //In case of multiple labels, we retain only the first one
            }
        }

        NSString *rssTitle = @"";
        if (feed[@"title"]) {
            rssTitle = feed[@"title"];
        }
        if (![localFeeds containsObject:feedID]) {
        // legacy search in stored URLs
            NSString *legacyKey;
            if (hostRequiresHexaForFeedId) {                     // TheOldReader
                NSString *identifier =
                  [feedID stringByReplacingOccurrencesOfString:@"feed/" withString:@"" options:0 range:NSMakeRange(0, 5)];
                legacyKey = [NSString stringWithFormat:@"%@://%@/reader/public/atom/%@", openReaderScheme, openReaderHost, identifier];
            } else {
                legacyKey = feedURL;
            }
            Folder *f = [db folderFromFeedURL:legacyKey];
            if (f && f.isOpenReaderFolder) {         // exists already, but we didn't store the remoteId
                [db setRemoteId:feedID forFolder:f.itemId];
                [db setFeedURL:feedURL forFolder:f.itemId];
            } else {
                // we need to create
                // folderName could be nil
                NSArray *params = folderName ? @[feedID, feedURL, rssTitle, folderName] : @[feedID, feedURL, rssTitle];
                [self createNewSubscription:params];
            }
        } else {
            // the feed is already known
            // set HomePage and other infos if they are available
            NSString *homePageURL = feed[@"htmlUrl"];
            for (Folder *localFolder in localFolders) {
                if (localFolder.isOpenReaderFolder && [localFolder.remoteId isEqualToString:feedID]) {
                    NSInteger nativeId = localFolder.itemId;
                    [db setFeedURL:feedURL forFolder:nativeId];
                    if (homePageURL) {
                        [db setHomePage:homePageURL forFolder:nativeId];
                    }
                    if ([db folderFromName:rssTitle] == nil) { // no conflict
                        [db setName:rssTitle forFolder:nativeId];
                    }
                    NSInteger neededParentId = [db folderFromName:folderName].itemId;
                    if (neededParentId == 0) {
                        neededParentId = VNAFolderTypeRoot;
                    }
                    if (localFolder.parentId != neededParentId) {
                        [[NSNotificationCenter defaultCenter] vna_postNotificationOnMainThreadWithName:MA_Notify_OpenReaderFolderChange
                                                              object:@[ [NSNumber numberWithInteger:nativeId],
                                                                        [NSNumber numberWithInteger:neededParentId],
                                                                        [NSNumber numberWithInteger:0]]];
                    }
                    break;
                }
            }
        }

        [remoteFeeds addObject:feedID];
    }

    if (subscriptionsDict[@"subscriptions"] != nil) { // detect probable authentication error
        //check if we have a folder which is not registered as a Open Reader feed
        for (Folder *f in localFolders) {
            if (f.isOpenReaderFolder) {
             	if ([remoteFeeds containsObject:f.remoteId]  && [f.remoteId hasPrefix:@"feed/"]) {
             		[f setNonPersistedFlag:VNAFolderFlagSyncedOK];
            	} else {
                	[[Database sharedManager] deleteFolder:f.itemId];
            	}
            }
        }
    }
} // subscriptionsRequestDone

// callback 2 to loadSubscriptions
-(void)unreadCountDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    NSDictionary *unreadDict;
    NSError *jsonError;
    unreadDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
    NSMutableArray *remoteUnreadFeeds = [NSMutableArray array];
    for (NSDictionary *feed in unreadDict[@"unreadcounts"]) {
        NSString *feedID = feed[@"id"];
        if ([feedID hasPrefix:@"feed/"]) {
            Folder *folder = [[Database sharedManager] folderFromRemoteId:feedID];
            if (folder) {
                [remoteUnreadFeeds addObject:feedID];
                NSInteger remoteCount = ((NSString *)feed[@"count"]).intValue;
                NSInteger localCount = folder.unreadCount;
                NSInteger remoteTimestamp = ((NSString *)feed[@"newestItemTimestampUsec"]).longLongValue / 1000000; // convert in truncated seconds
                NSString *folderLastUpdateString = folder.lastUpdateString;
                if (folderLastUpdateString == nil || [folderLastUpdateString isEqualToString:@""] ||
                    [folderLastUpdateString isEqualToString:@"(null)"])
                {
                    folderLastUpdateString = @"0";
                }
                NSInteger localTimestamp = folderLastUpdateString.longLongValue;
                if (remoteTimestamp > localTimestamp || remoteCount != localCount) {
                    // discrepancy between local feed and remote OpenReader server
                    [folder clearNonPersistedFlag:VNAFolderFlagSyncedOK];
                }
            }
        }
    }

    NSArray *localFolders = [Database sharedManager].arrayOfAllFolders;
    for (Folder *folder in localFolders) {
        // folders which are locally marked as having unread articles, while they are remotely all read
        if (folder.isOpenReaderFolder && folder.unreadCount > 0 && ![remoteUnreadFeeds containsObject:folder.remoteId]) {
            [folder clearNonPersistedFlag:VNAFolderFlagSyncedOK];
        }
    }
} // unreadCountDone

-(void)subscribeToFeed:(NSString *)feedURL withLabel:(NSString *)label
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@subscription/quickadd?client=%@", APIBaseURL, ClientName]];

    NSMutableURLRequest *request = [self authentifiedFormRequestFromURL:url];
    [request vna_setPostValue:feedURL forKey:@"quickadd"];
    [request vna_setInUserInfo:label  forKey:@"label"];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:request
        completionHandler :^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:request response:response error:error];
            } else {
                [weakSelf subscribeToFeedDone:request response:response data:data];
            }
        }
    ];
}

-(void)subscribeToFeedDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    NSDictionary *responseDict;
    NSError *jsonError;
    responseDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
    NSString *label = ((NSDictionary *)[request vna_userInfo])[@"label"];
    NSString * feedIdentifier = responseDict[@"streamId"];
    if (feedIdentifier) {
        if (label) {
            [self setFolderLabel:label forFeed:feedIdentifier set:TRUE];
        }
	    [self loadSubscriptions];
    }
}

-(void)unsubscribeFromFeedIdentifier:(NSString *)feedIdentifier
{
    NSURL *unsubscribeURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@subscription/edit", APIBaseURL]];
    NSMutableURLRequest *myRequest = [self authentifiedFormRequestFromURL:unsubscribeURL];
    [myRequest vna_setPostValue:@"unsubscribe" forKey:@"ac"];
    [myRequest vna_setPostValue:feedIdentifier forKey:@"s"];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:myRequest
        completionHandler :^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:myRequest response:response error:error];
            } else {
                [weakSelf requestFinished:myRequest response:response data:data];
            }
        }
    ];
}

/* setFolderLabel:forFeed:set:
 * add or remove a label (folder name) to a newsfeed
 * set parameter : TRUE => add ; FALSE => remove
 */
-(void)setFolderLabel:(NSString *)folderName forFeed:(NSString *)feedIdentifier set:(BOOL)flag
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@subscription/edit?client=%@", APIBaseURL, ClientName]];

    NSMutableURLRequest *request = [self authentifiedFormRequestFromURL:url];
    [request vna_setPostValue:@"edit" forKey:@"ac"];
    [request vna_setPostValue:feedIdentifier forKey:@"s"];
    [request vna_setPostValue:[NSString stringWithFormat:@"user/-/label/%@", folderName] forKey:flag ? @"a" : @"r"];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:request
        completionHandler :^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:request response:response error:error];
            } else {
                [weakSelf requestFinished:request response:response data:data];
            }
        }
    ];
}

/* setFolderTitle:forFeed:
 * set title of a newsfeed
 */
-(void)setFolderTitle:(NSString *)folderName forFeed:(NSString *)feedIdentifier
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@subscription/edit?client=%@", APIBaseURL, ClientName]];

    NSMutableURLRequest *request = [self authentifiedFormRequestFromURL:url];
    [request vna_setPostValue:@"edit" forKey:@"ac"];
    [request vna_setPostValue:feedIdentifier forKey:@"s"];
    [request vna_setPostValue:folderName forKey:@"t"];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:request
        completionHandler :^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:request response:response error:error];
            } else {
                [weakSelf requestFinished:request response:response data:data];
            }
        }
    ];
}

-(void)markRead:(Article *)article readFlag:(BOOL)flag
{
    NSURL *markReadURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@edit-tag", APIBaseURL]];
    NSMutableURLRequest *myRequest = [self authentifiedFormRequestFromURL:markReadURL];
    if (flag) {
        [myRequest vna_setPostValue:@"user/-/state/com.google/read" forKey:@"a"];
    } else {
        [myRequest vna_setPostValue:@"user/-/state/com.google/read" forKey:@"r"];
    }
    [myRequest vna_setPostValue:@"true" forKey:@"async"];
    [myRequest vna_setPostValue:article.guid forKey:@"i"];
    [myRequest vna_addInfoFromDictionary:@{ @"article": article, @"readFlag": @(flag) }];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:myRequest
        completionHandler :^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:myRequest response:response error:error];
            } else {
                [weakSelf markReadDone:myRequest response:response data:data];
            }
        }
    ];
} // markRead

// callback : we check if the server did confirm the read status change
-(void)markReadDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    NSString *requestResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if ([requestResponse isEqualToString:@"OK"]) {
        Article *article = ((NSDictionary *)[request vna_userInfo])[@"article"];
        BOOL readFlag = [[((NSDictionary *)[request vna_userInfo]) valueForKey:@"readFlag"] boolValue];
        [[Database sharedManager] markArticleRead:article.folderId guid:article.guid isRead:readFlag];
        article.read = readFlag;
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc vna_postNotificationOnMainThreadWithName:MA_Notify_ArticleListStateChange object:@(article.folderId)];
    }
}

-(void)markAllReadInFolder:(Folder *)folder
{
    NSURL *markReadURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@mark-all-as-read", APIBaseURL]];
    NSMutableURLRequest *request = [self authentifiedFormRequestFromURL:markReadURL];
    [request vna_setUserInfo: @{ @"folder": folder}];
    NSString *feedIdentifier = folder.remoteId;
    [request vna_setPostValue:feedIdentifier forKey:@"s"];
    NSString *folderLastUpdateString = folder.lastUpdateString;
    //This is a workaround throw a BAD folderupdate value on DB
    if (folderLastUpdateString == nil || [folderLastUpdateString isEqualToString:@""] ||
        [folderLastUpdateString isEqualToString:@"(null)"])
    {
        folderLastUpdateString = @"0";
    }
    NSInteger localTimestamp = (folderLastUpdateString.longLongValue +1)* 1000000 ; // next second converted to microseconds
    NSString * microsecondsUpdateString = @(localTimestamp).stringValue; // string value of NSNumber
    [request vna_setPostValue:microsecondsUpdateString forKey:@"ts"];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:request
		completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
			if (error) {
			[weakSelf requestFailed:request response:response error:error];
			} else {
			[weakSelf markAllReadDone:request response:response data:data];
			}
		}
    ];
} // markAllReadInFolder

// callback : we check if the server did confirm the read status change
-(void)markAllReadDone:(NSMutableURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data
{
    NSString *requestResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if ([requestResponse isEqualToString:@"OK"]) {
        Folder *folder = ((NSDictionary *)[request vna_userInfo])[@"folder"];
        [[Database sharedManager] markFolderRead:folder.itemId];
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc vna_postNotificationOnMainThreadWithName:MA_Notify_ArticleListStateChange object:@(folder.itemId)];
    }
}

-(void)markStarred:(Article *)article starredFlag:(BOOL)flag
{
    NSURL *markStarredURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@edit-tag", APIBaseURL]];
    NSMutableURLRequest *myRequest = [self authentifiedFormRequestFromURL:markStarredURL];
    if (flag) {
        [myRequest vna_setPostValue:@"user/-/state/com.google/starred" forKey:@"a"];
    } else {
        [myRequest vna_setPostValue:@"user/-/state/com.google/starred" forKey:@"r"];
    }
    [myRequest vna_setPostValue:@"true" forKey:@"async"];
    [myRequest vna_setPostValue:article.guid forKey:@"i"];
    __weak typeof(self) weakSelf = self;
    [[RefreshManager sharedManager] addConnection:myRequest
        completionHandler :^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                [weakSelf requestFailed:myRequest response:response error:error];
            } else {
                [weakSelf requestFinished:myRequest response:response data:data];
            }
        }
    ];
} // markStarred

-(void)createNewSubscription:(NSArray *)params
{
    NSInteger underFolder = VNAFolderTypeRoot;
    NSString *feedID = params[0];
    NSString *feedURL = params[1];
    NSString *rssTitle = [NSString stringWithFormat:@""];
    Database *db = [Database sharedManager];

    if (params.count > 2) {
        rssTitle = params[2];
        if (params.count > 3) {
            NSString *folderName = params[3];
            Folder *folder = [db folderFromName:folderName];
            underFolder = folder.itemId;
        }
    }

    [db addOpenReaderFolder:rssTitle underParent:underFolder afterChild:-1 subscriptionURL:feedURL remoteId:feedID];
}

-(void)createFolders:(NSMutableArray *)params
{
    NSMutableArray *folderNames = params[0];
    NSNumber *parentNumber = params[1];

    // Remove the parent parameter. We'll re-add it with a new value later.
    [params removeObjectAtIndex:1];

    Database *dbManager = [Database sharedManager];
    NSString *folderName = folderNames[0];
    Folder *folder = [dbManager folderFromName:folderName];

    if (!folder) {
        NSInteger newFolderId;
        newFolderId =
            [dbManager addFolder:parentNumber.integerValue afterChild:-1 folderName:folderName type:VNAFolderTypeGroup canAppendIndex:NO];
        parentNumber = @(newFolderId);
    } else {
        parentNumber = @(folder.itemId);
    }

    [folderNames removeObjectAtIndex:0];
    if (folderNames.count > 0) {
        // Add the new parent parameter.
        [params addObject:parentNumber];
        [self createFolders:params];
    }
} // createFolders

// Escape invalid and reserved URL characters to make string suitable for
// embedding in mailto: URLs and custom app-specific schemes like papers://
// cf unreserved characters in section 2.3 of RFC3986
+ (NSString *)escapeFeedId:(NSString *)identifier
{
    NSString *shortenedStr = [identifier stringByReplacingOccurrencesOfString:@"feed/"
                                                                   withString:@""
                                                                      options:0
                                                                        range:NSMakeRange(0, 5)];
    NSString *chars = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:chars];
    NSString *escapedStr = [shortenedStr stringByAddingPercentEncodingWithAllowedCharacters:set];

    return [NSString stringWithFormat:@"feed/%@", escapedStr];
}

@end
